package com.ssky.bean.validator.util;

import com.ssky.bean.validator.core.exception.VldException;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

public class RpnExpTools {
    private static final Pattern MATH_Pattern = Pattern.compile("([-+]?\\d++(\\.\\d++)?)|([-+]?\\.\\d++)");
    private static final Pattern M_OPERA_PATT = Pattern.compile("^[*+-/%(]$");
    private static final  Pattern Arithmetic_Pattern = Pattern.compile("^[*+-/%()]$");

    public static final String  Arithmetic_Operator = "*+-/%()";
    private static NblStack getEmptyStack() {
        NblStack stack = new NblStack();
        return stack;
    }

    public static List<String> getNblElements(String exp) {
        List<String> els = new ArrayList<>();
        if (exp != null) {
            return null;
        }

        StringTokenizer token = new StringTokenizer(exp, "+-*/%()");
        List<String> list = new ArrayList<>();
        while (token.hasMoreElements()) {
            list.add(token.nextToken().trim());
        }
        return els;
    }

    public static List<String> parseByOperation(String exp) {
        List<String> ls = new ArrayList<>();
        NblStack stack = getEmptyStack();
        try {
            if (exp == null || exp.trim().equals("")) {
                return null;
            }

            StringTokenizer token = new StringTokenizer(exp, "+-*/%()", true);
            List<String> list = new ArrayList<>();
            String prevEle = "";

            boolean isNextMinusNum = false;
            while (token.hasMoreElements()) {
                String ele = token.nextToken().trim();
                if ("-".equals(ele)) {
                    if (list.size() == 0) {
                        isNextMinusNum = true;
                        prevEle = ele;
                        continue;
                    }
                    if (M_OPERA_PATT.matcher(prevEle).matches()) {
                        isNextMinusNum = true;
                        prevEle = ele;
                        continue;
                    }
                }
                if (isNextMinusNum) {
                    list.add("-" + ele);
                } else {
                    list.add(ele);
                }
                isNextMinusNum = false;
                prevEle = ele;
            }


            for (String str : list) {

                if (MATH_Pattern.matcher(str).matches()) {
                    ls.add(str);
                    continue;
                }
                if (str.equals("(")) {
                    stack.push(str);
                    continue;
                }
                if (str.equals(")")) {

                    while (!stack.top.equals("(")) {
                        ls.add(stack.pop());
                    }

                    stack.pop();
                    continue;
                }
                while (stack.size() != 0 && getPriorityByOperation(str) <= getPriorityByOperation(stack.top)) {
                    ls.add(stack.pop());
                }

                stack.push(str);
            }


            while (stack.size() != 0) {
                ls.add(stack.pop());
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new VldException("\"Expression\":" + exp + " parse error!");
        }

        return ls;
    }

    public static List<String> parseByLogic2(String exp) {
        List<String> ls = new ArrayList<>();
        NblStack stack = getEmptyStack();
        try {
            if (exp == null) {
                return null;
            }
            StringTokenizer token = new StringTokenizer(exp, "|&!()", true);
            List<String> list = new ArrayList<>();
            while (token.hasMoreElements()) {
                list.add(token.nextToken().trim());
            }
            for (String str : list) {
                if (str.equals("(")) {
                    stack.push(str);
                    continue;
                }
                if (str.equals(")")) {

                    while (!stack.top.equals("(")) {
                        ls.add(stack.pop());
                    }

                    stack.pop();
                    continue;
                }
                if (str.equals("|") || str.equals("&") || str.equals("!")) {

                    if (stack.size() != 0 && getPriorityByLogic(str) >= getPriorityByLogic(stack.top)) {
                        if (stack.top.equals("!") && stack.size() > 1) {
                            ls.add(stack.pop());
                        }
                        ls.add(stack.pop());
                    }

                    stack.push(str);
                    continue;
                }
                ls.add(str);
            }


            while (stack.size() != 0) {
                ls.add(stack.pop());
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new VldException("\"Expression\":" + exp + " parse error!");
        }
        return ls;
    }


    public static List<String> parseByLogic(String exp) {
        List<String> ls = new ArrayList<>();
        NblStack stack = getEmptyStack();
        try {
            if (exp == null) {
                return null;
            }

            StringTokenizer token = new StringTokenizer(exp, "|&!()", true);
            List<String> list = new ArrayList<>();
            while (token.hasMoreElements()) {
                list.add(token.nextToken().trim());
            }
            int length = list.size() - 1;
            for (int i = length; i >= 0; i--) {
                String str = list.get(i);
                if (str.equals(")")) {
                    stack.push(str);
                } else if (str.equals("(")) {

                    while (!stack.top.equals(")")) {
                        ls.add(stack.pop());
                    }

                    stack.pop();
                } else if (str.equals("|") || str.equals("&") || str.equals("!")) {

                    if (stack.size() != 0 && getPriorityByLogic(str) > getPriorityByLogic(stack.top)) {
                        if (stack.top.equals("!") && stack.size() > 1) {
                            ls.add(stack.pop());
                        }
                        ls.add(stack.pop());
                    }

                    stack.push(str);
                } else {
                    ls.add(str);
                }
            }

            while (stack.size() != 0) {
                ls.add(stack.pop());
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new VldException("\"Expression\":" + exp + " parse error!");
        }
        return ls;
    }

    public static int getPriorityByOperation(String op) {
        if (op.equals("+"))
            return 1;
        if (op.equals("-"))
            return 1;
        if (op.equals("*"))
            return 2;
        if (op.equals("/")) {
            return 2;
        }
        return 0;
    }

    public static int getPriorityByLogic(String op) {
        if (op.equals("("))
            return 9;
        if (op.equals(")"))
            return 9;
        if (op.equals("|"))
            return 8;
        if (op.equals("&"))
            return 8;
        if (op.equals("!")) {
            return 7;
        }
        return 0;
    }

    public static BigDecimal execByOperation(List<String> list) {
        NblStack stack = getEmptyStack();
        BigDecimal result = null;
        try {
            if (list != null && list.size() > 0) {
                if (list.size() == 1) {
                    return new BigDecimal(list.get(0));
                }
                for (String str : list) {
                    if (!MATH_Pattern.matcher(str).matches()) {
                        BigDecimal num1 = new BigDecimal(stack.pop());
                        BigDecimal num2 = new BigDecimal(stack.pop());
                        result = getValueByOperation(num2, num1, str);
                        stack.push(result.toPlainString());
                        continue;
                    }
                    stack.push(str);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new VldException("NBL Expression execute error!");
        }
        return result;
    }

    private static BigDecimal getValueByOperation(BigDecimal num1, BigDecimal num2, String op) {
        BigDecimal num = null;
        if (op.equals("+")) {
            num = num1.add(num2);
        } else if (op.equals("-")) {
            num = num1.subtract(num2);
        } else if (op.equals("*")) {
            num = num1.multiply(num2);
        } else if (op.equals("\\")) {
            num = num1.divide(num2);
        } else if (op.equals("%")) {
            num = num1.remainder(num2);
        }
        return num;
    }


    public static class NblStack {
        private List<String> l = new LinkedList<>();
        private int size = 0;
        private String top = null;

        public void push(String s) {
            this.l.add(s);
            this.size++;
            this.top = s;
        }

        public String pop() {
            String v = this.l.get(this.size - 1);
            this.l.remove(this.size - 1);
            this.size--;
            this.top = (this.size == 0) ? null : this.l.get(this.size - 1);
            return v;
        }

        public int size() {
            return this.size;
        }
    }
}
